1
|
|
|
import $ from 'jquery'; |
|
|
|
|
2
|
|
|
import _ from 'underscore'; |
3
|
|
|
import { |
4
|
|
|
Backbone, |
5
|
|
|
wrapError, |
6
|
|
|
addUnderscoreMethods |
7
|
|
|
} from './core.js'; |
8
|
|
|
import { |
9
|
|
|
Events |
10
|
|
|
} from './events.js'; |
11
|
|
|
import { |
12
|
|
|
Model |
13
|
|
|
} from './model.js'; |
14
|
|
|
|
15
|
|
|
// Create a local reference to a common array method we'll want to use later. |
16
|
|
|
var slice = Array.prototype.slice; |
17
|
|
|
|
18
|
|
|
// Backbone.Collection |
19
|
|
|
// ------------------- |
20
|
|
|
|
21
|
|
|
// If models tend to represent a single row of data, a Backbone Collection is |
22
|
|
|
// more analogous to a table full of data ... or a small slice or page of that |
23
|
|
|
// table, or a collection of rows that belong together for a particular reason |
24
|
|
|
// -- all of the messages in this particular folder, all of the documents |
25
|
|
|
// belonging to this particular author, and so on. Collections maintain |
26
|
|
|
// indexes of their models, both in order, and for lookup by `id`. |
27
|
|
|
|
28
|
|
|
// Create a new **Collection**, perhaps to contain a specific type of `model`. |
29
|
|
|
// If a `comparator` is specified, the Collection will maintain |
30
|
|
|
// its models in sort order, as they're added and removed. |
31
|
|
|
var Collection = function (models, options) { |
32
|
|
|
options = options || {}; |
33
|
|
|
this.preinitialize.apply(this, arguments); |
34
|
|
|
if (options.model) { |
35
|
|
|
this.model = options.model; |
36
|
|
|
} |
37
|
|
|
if (options.comparator !== void 0) { |
38
|
|
|
this.comparator = options.comparator; |
39
|
|
|
} |
40
|
|
|
this._reset(); |
41
|
|
|
this.initialize.apply(this, arguments); |
42
|
|
|
if (models) { |
43
|
|
|
this.reset(models, _.extend({ |
44
|
|
|
silent: true |
45
|
|
|
}, options)); |
46
|
|
|
} |
47
|
|
|
}; |
48
|
|
|
|
49
|
|
|
// Default options for `Collection#set`. |
50
|
|
|
var setOptions = { |
51
|
|
|
add: true, |
52
|
|
|
remove: true, |
53
|
|
|
merge: true |
54
|
|
|
}; |
55
|
|
|
var addOptions = { |
56
|
|
|
add: true, |
57
|
|
|
remove: false |
58
|
|
|
}; |
59
|
|
|
|
60
|
|
|
// Splices `insert` into `array` at index `at`. |
61
|
|
|
var splice = function (array, insert, at) { |
62
|
|
|
at = Math.min(Math.max(at, 0), array.length); |
63
|
|
|
var tail = Array(array.length - at); |
64
|
|
|
var length = insert.length; |
65
|
|
|
var i; |
66
|
|
|
for (i = 0; i < tail.length; i++) { |
67
|
|
|
tail[i] = array[i + at]; |
68
|
|
|
} |
69
|
|
|
for (i = 0; i < length; i++) { |
70
|
|
|
array[i + at] = insert[i]; |
71
|
|
|
} |
72
|
|
|
for (i = 0; i < tail.length; i++) { |
73
|
|
|
array[i + length + at] = tail[i]; |
74
|
|
|
} |
75
|
|
|
}; |
76
|
|
|
|
77
|
|
|
// Define the Collection's inheritable methods. |
78
|
|
|
_.extend(Collection.prototype, Events, { |
79
|
|
|
|
80
|
|
|
// The default model for a collection is just a **Backbone.Model**. |
81
|
|
|
// This should be overridden in most cases. |
82
|
|
|
model: Model, |
83
|
|
|
|
84
|
|
|
// preinitialize is an empty function by default. You can override it with a function |
85
|
|
|
// or object. preinitialize will run before any instantiation logic is run in the Collection. |
86
|
|
|
preinitialize: function () {}, |
87
|
|
|
|
88
|
|
|
// Initialize is an empty function by default. Override it with your own |
89
|
|
|
// initialization logic. |
90
|
|
|
initialize: function () {}, |
91
|
|
|
|
92
|
|
|
// The JSON representation of a Collection is an array of the |
93
|
|
|
// models' attributes. |
94
|
|
|
toJSON: function (options) { |
95
|
|
|
return this.map(function (model) { |
96
|
|
|
return model.toJSON(options); |
97
|
|
|
}); |
98
|
|
|
}, |
99
|
|
|
|
100
|
|
|
// Proxy `Backbone.sync` by default. |
101
|
|
|
sync: function () { |
102
|
|
|
return Backbone.sync.apply(this, arguments); |
103
|
|
|
}, |
104
|
|
|
|
105
|
|
|
// Add a model, or list of models to the set. `models` may be Backbone |
106
|
|
|
// Models or raw JavaScript objects to be converted to Models, or any |
107
|
|
|
// combination of the two. |
108
|
|
|
add: function (models, options) { |
109
|
|
|
return this.set(models, _.extend({ |
110
|
|
|
merge: false |
111
|
|
|
}, options, addOptions)); |
112
|
|
|
}, |
113
|
|
|
|
114
|
|
|
// Remove a model, or a list of models from the set. |
115
|
|
|
remove: function (models, options) { |
116
|
|
|
options = _.extend({}, options); |
117
|
|
|
var singular = !_.isArray(models); |
118
|
|
|
models = singular ? [models] : models.slice(); |
119
|
|
|
var removed = this._removeModels(models, options); |
120
|
|
|
if (!options.silent && removed.length) { |
121
|
|
|
options.changes = { |
122
|
|
|
added: [], |
123
|
|
|
merged: [], |
124
|
|
|
removed: removed |
125
|
|
|
}; |
126
|
|
|
this.trigger('update', this, options); |
127
|
|
|
} |
128
|
|
|
return singular ? removed[0] : removed; |
129
|
|
|
}, |
130
|
|
|
|
131
|
|
|
// Update a collection by `set`-ing a new list of models, adding new ones, |
132
|
|
|
// removing models that are no longer present, and merging models that |
133
|
|
|
// already exist in the collection, as necessary. Similar to **Model#set**, |
134
|
|
|
// the core operation for updating the data contained by the collection. |
135
|
|
|
set: function (models, options) { |
136
|
|
|
if (models == null) { |
137
|
|
|
return; |
138
|
|
|
} |
139
|
|
|
|
140
|
|
|
options = _.extend({}, setOptions, options); |
141
|
|
|
if (options.parse && !this._isModel(models)) { |
142
|
|
|
models = this.parse(models, options) || []; |
143
|
|
|
} |
144
|
|
|
|
145
|
|
|
var singular = !_.isArray(models); |
146
|
|
|
models = singular ? [models] : models.slice(); |
147
|
|
|
|
148
|
|
|
var at = options.at; |
149
|
|
|
if (at != null) { |
150
|
|
|
at = +at; |
151
|
|
|
} |
152
|
|
|
if (at > this.length) { |
153
|
|
|
at = this.length; |
154
|
|
|
} |
155
|
|
|
if (at < 0) { |
156
|
|
|
at += this.length + 1; |
157
|
|
|
} |
158
|
|
|
|
159
|
|
|
var set = []; |
160
|
|
|
var toAdd = []; |
161
|
|
|
var toMerge = []; |
162
|
|
|
var toRemove = []; |
163
|
|
|
var modelMap = {}; |
164
|
|
|
|
165
|
|
|
var add = options.add; |
166
|
|
|
var merge = options.merge; |
167
|
|
|
var remove = options.remove; |
168
|
|
|
|
169
|
|
|
var sort = false; |
170
|
|
|
var sortable = this.comparator && at == null && options.sort !== |
171
|
|
|
false; |
172
|
|
|
var sortAttr = _.isString(this.comparator) ? this.comparator : |
173
|
|
|
null; |
174
|
|
|
|
175
|
|
|
// Turn bare objects into model references, and prevent invalid models |
176
|
|
|
// from being added. |
177
|
|
|
var model, i; |
178
|
|
|
for (i = 0; i < models.length; i++) { |
179
|
|
|
model = models[i]; |
180
|
|
|
|
181
|
|
|
// If a duplicate is found, prevent it from being added and |
182
|
|
|
// optionally merge it into the existing model. |
183
|
|
|
var existing = this.get(model); |
184
|
|
|
if (existing) { |
185
|
|
|
if (merge && model !== existing) { |
186
|
|
|
var attrs = this._isModel(model) ? model.attributes : |
187
|
|
|
model; |
188
|
|
|
if (options.parse) { |
|
|
|
|
189
|
|
|
attrs = existing.parse(attrs, |
190
|
|
|
options); |
191
|
|
|
} |
192
|
|
|
existing.set(attrs, options); |
193
|
|
|
toMerge.push(existing); |
194
|
|
|
if (sortable && !sort) { |
|
|
|
|
195
|
|
|
sort = existing.hasChanged( |
196
|
|
|
sortAttr); |
197
|
|
|
} |
198
|
|
|
} |
199
|
|
|
if (!modelMap[existing.cid]) { |
200
|
|
|
modelMap[existing.cid] = true; |
201
|
|
|
set.push(existing); |
202
|
|
|
} |
203
|
|
|
models[i] = existing; |
204
|
|
|
|
205
|
|
|
// If this is a new, valid model, push it to the `toAdd` list. |
206
|
|
|
} else if (add) { |
207
|
|
|
model = models[i] = this._prepareModel(model, options); |
208
|
|
|
if (model) { |
209
|
|
|
toAdd.push(model); |
210
|
|
|
this._addReference(model, options); |
211
|
|
|
modelMap[model.cid] = true; |
212
|
|
|
set.push(model); |
213
|
|
|
} |
214
|
|
|
} |
215
|
|
|
} |
216
|
|
|
|
217
|
|
|
// Remove stale models. |
218
|
|
|
if (remove) { |
219
|
|
|
for (i = 0; i < this.length; i++) { |
220
|
|
|
model = this.models[i]; |
221
|
|
|
if (!modelMap[model.cid]) { |
222
|
|
|
toRemove.push(model); |
223
|
|
|
} |
224
|
|
|
} |
225
|
|
|
if (toRemove.length) { |
226
|
|
|
this._removeModels(toRemove, options); |
227
|
|
|
} |
228
|
|
|
} |
229
|
|
|
|
230
|
|
|
// See if sorting is needed, update `length` and splice in new models. |
231
|
|
|
var orderChanged = false; |
232
|
|
|
var replace = !sortable && add && remove; |
233
|
|
|
if (set.length && replace) { |
234
|
|
|
orderChanged = this.length !== set.length || _.some(this.models, |
235
|
|
|
function (m, index) { |
236
|
|
|
return m !== set[index]; |
237
|
|
|
}); |
238
|
|
|
this.models.length = 0; |
239
|
|
|
splice(this.models, set, 0); |
240
|
|
|
this.length = this.models.length; |
241
|
|
|
} else if (toAdd.length) { |
242
|
|
|
if (sortable) { |
243
|
|
|
sort = true; |
244
|
|
|
} |
245
|
|
|
splice(this.models, toAdd, at == null ? this.length : at); |
246
|
|
|
this.length = this.models.length; |
247
|
|
|
} |
248
|
|
|
|
249
|
|
|
// Silently sort the collection if appropriate. |
250
|
|
|
if (sort) { |
251
|
|
|
this.sort({ |
252
|
|
|
silent: true |
253
|
|
|
}); |
254
|
|
|
} |
255
|
|
|
|
256
|
|
|
// Unless silenced, it's time to fire all appropriate add/sort/update events. |
257
|
|
|
if (!options.silent) { |
258
|
|
|
for (i = 0; i < toAdd.length; i++) { |
259
|
|
|
if (at != null) { |
260
|
|
|
options.index = at + i; |
261
|
|
|
} |
262
|
|
|
model = toAdd[i]; |
263
|
|
|
model.trigger('add', model, this, options); |
264
|
|
|
} |
265
|
|
|
if (sort || orderChanged) { |
266
|
|
|
this.trigger('sort', this, |
267
|
|
|
options); |
268
|
|
|
} |
269
|
|
|
if (toAdd.length || toRemove.length || toMerge.length) { |
270
|
|
|
options.changes = { |
271
|
|
|
added: toAdd, |
272
|
|
|
removed: toRemove, |
273
|
|
|
merged: toMerge |
274
|
|
|
}; |
275
|
|
|
this.trigger('update', this, options); |
276
|
|
|
} |
277
|
|
|
} |
278
|
|
|
|
279
|
|
|
// Return the added (or merged) model (or models). |
280
|
|
|
return singular ? models[0] : models; |
281
|
|
|
}, |
282
|
|
|
|
283
|
|
|
// When you have more items than you want to add or remove individually, |
284
|
|
|
// you can reset the entire set with a new list of models, without firing |
285
|
|
|
// any granular `add` or `remove` events. Fires `reset` when finished. |
286
|
|
|
// Useful for bulk operations and optimizations. |
287
|
|
|
reset: function (models, options) { |
288
|
|
|
options = options ? _.clone(options) : {}; |
289
|
|
|
for (var i = 0; i < this.models.length; i++) { |
290
|
|
|
this._removeReference(this.models[i], options); |
291
|
|
|
} |
292
|
|
|
options.previousModels = this.models; |
293
|
|
|
this._reset(); |
294
|
|
|
models = this.add(models, _.extend({ |
295
|
|
|
silent: true |
296
|
|
|
}, options)); |
297
|
|
|
if (!options.silent) { |
298
|
|
|
this.trigger('reset', this, options); |
299
|
|
|
} |
300
|
|
|
return models; |
301
|
|
|
}, |
302
|
|
|
|
303
|
|
|
// Add a model to the end of the collection. |
304
|
|
|
push: function (model, options) { |
305
|
|
|
return this.add(model, _.extend({ |
306
|
|
|
at: this.length |
307
|
|
|
}, options)); |
308
|
|
|
}, |
309
|
|
|
|
310
|
|
|
// Remove a model from the end of the collection. |
311
|
|
|
pop: function (options) { |
312
|
|
|
var model = this.at(this.length - 1); |
313
|
|
|
return this.remove(model, options); |
314
|
|
|
}, |
315
|
|
|
|
316
|
|
|
// Add a model to the beginning of the collection. |
317
|
|
|
unshift: function (model, options) { |
318
|
|
|
return this.add(model, _.extend({ |
319
|
|
|
at: 0 |
320
|
|
|
}, options)); |
321
|
|
|
}, |
322
|
|
|
|
323
|
|
|
// Remove a model from the beginning of the collection. |
324
|
|
|
shift: function (options) { |
325
|
|
|
var model = this.at(0); |
326
|
|
|
return this.remove(model, options); |
327
|
|
|
}, |
328
|
|
|
|
329
|
|
|
// Slice out a sub-array of models from the collection. |
330
|
|
|
slice: function () { |
331
|
|
|
return slice.apply(this.models, arguments); |
332
|
|
|
}, |
333
|
|
|
|
334
|
|
|
// Get a model from the set by id, cid, model object with id or cid |
335
|
|
|
// properties, or an attributes object that is transformed through modelId. |
336
|
|
|
get: function (obj) { |
337
|
|
|
if (obj == null) { |
338
|
|
|
return void 0; |
339
|
|
|
} |
340
|
|
|
return this._byId[obj] || |
341
|
|
|
this._byId[this.modelId(obj.attributes || obj)] || |
342
|
|
|
obj.cid && this._byId[obj.cid]; |
343
|
|
|
}, |
344
|
|
|
|
345
|
|
|
// Returns `true` if the model is in the collection. |
346
|
|
|
has: function (obj) { |
347
|
|
|
return this.get(obj) != null; |
348
|
|
|
}, |
349
|
|
|
|
350
|
|
|
// Get the model at the given index. |
351
|
|
|
at: function (index) { |
352
|
|
|
if (index < 0) { |
353
|
|
|
index += this.length; |
354
|
|
|
} |
355
|
|
|
return this.models[index]; |
356
|
|
|
}, |
357
|
|
|
|
358
|
|
|
// Return models with matching attributes. Useful for simple cases of |
359
|
|
|
// `filter`. |
360
|
|
|
where: function (attrs, first) { |
361
|
|
|
return this[first ? 'find' : 'filter'](attrs); |
362
|
|
|
}, |
363
|
|
|
|
364
|
|
|
// Return the first model with matching attributes. Useful for simple cases |
365
|
|
|
// of `find`. |
366
|
|
|
findWhere: function (attrs) { |
367
|
|
|
return this.where(attrs, true); |
368
|
|
|
}, |
369
|
|
|
|
370
|
|
|
// Force the collection to re-sort itself. You don't need to call this under |
371
|
|
|
// normal circumstances, as the set will maintain sort order as each item |
372
|
|
|
// is added. |
373
|
|
|
sort: function (options) { |
374
|
|
|
var comparator = this.comparator; |
375
|
|
|
if (!comparator) { |
376
|
|
|
throw new Error( |
377
|
|
|
'Cannot sort a set without a comparator'); |
378
|
|
|
} |
379
|
|
|
options = options || {}; |
380
|
|
|
|
381
|
|
|
var length = comparator.length; |
382
|
|
|
if (_.isFunction(comparator)) { |
383
|
|
|
comparator = _.bind( |
384
|
|
|
comparator, |
385
|
|
|
this); |
386
|
|
|
} |
387
|
|
|
|
388
|
|
|
// Run sort based on type of `comparator`. |
389
|
|
|
if (length === 1 || _.isString(comparator)) { |
390
|
|
|
this.models = this.sortBy(comparator); |
391
|
|
|
} else { |
392
|
|
|
this.models.sort(comparator); |
393
|
|
|
} |
394
|
|
|
if (!options.silent) { |
395
|
|
|
this.trigger('sort', this, options); |
396
|
|
|
} |
397
|
|
|
return this; |
398
|
|
|
}, |
399
|
|
|
|
400
|
|
|
// Pluck an attribute from each model in the collection. |
401
|
|
|
pluck: function (attr) { |
402
|
|
|
return this.map(attr + ''); |
403
|
|
|
}, |
404
|
|
|
|
405
|
|
|
// Fetch the default set of models for this collection, resetting the |
406
|
|
|
// collection when they arrive. If `reset: true` is passed, the response |
407
|
|
|
// data will be passed through the `reset` method instead of `set`. |
408
|
|
|
fetch: function (options) { |
409
|
|
|
options = _.extend({ |
410
|
|
|
parse: true |
411
|
|
|
}, options); |
412
|
|
|
var success = options.success; |
413
|
|
|
var collection = this; |
|
|
|
|
414
|
|
|
options.success = function (resp) { |
415
|
|
|
var method = options.reset ? 'reset' : 'set'; |
416
|
|
|
collection[method](resp, options); |
417
|
|
|
if (success) { |
418
|
|
|
success.call(options.context, collection, |
419
|
|
|
resp, |
420
|
|
|
options); |
421
|
|
|
} |
422
|
|
|
collection.trigger('sync', collection, resp, options); |
423
|
|
|
}; |
424
|
|
|
wrapError(this, options); |
425
|
|
|
return this.sync('read', this, options); |
426
|
|
|
}, |
427
|
|
|
|
428
|
|
|
// Create a new instance of a model in this collection. Add the model to the |
429
|
|
|
// collection immediately, unless `wait: true` is passed, in which case we |
430
|
|
|
// wait for the server to agree. |
431
|
|
|
create: function (model, options) { |
432
|
|
|
options = options ? _.clone(options) : {}; |
433
|
|
|
var wait = options.wait; |
434
|
|
|
model = this._prepareModel(model, options); |
435
|
|
|
if (!model) { |
436
|
|
|
return false; |
437
|
|
|
} |
438
|
|
|
if (!wait) { |
439
|
|
|
this.add(model, options); |
440
|
|
|
} |
441
|
|
|
var collection = this; |
|
|
|
|
442
|
|
|
var success = options.success; |
443
|
|
|
options.success = function (m, resp, callbackOpts) { |
444
|
|
|
if (wait) { |
445
|
|
|
collection.add(m, callbackOpts); |
446
|
|
|
} |
447
|
|
|
if (success) { |
448
|
|
|
success.call(callbackOpts.context, m, resp, |
449
|
|
|
callbackOpts); |
450
|
|
|
} |
451
|
|
|
}; |
452
|
|
|
model.save(null, options); |
453
|
|
|
return model; |
454
|
|
|
}, |
455
|
|
|
|
456
|
|
|
// **parse** converts a response into a list of models to be added to the |
457
|
|
|
// collection. The default implementation is just to pass it through. |
458
|
|
|
parse: function (resp, options) { |
|
|
|
|
459
|
|
|
return resp; |
460
|
|
|
}, |
461
|
|
|
|
462
|
|
|
// Create a new collection with an identical list of models as this one. |
463
|
|
|
clone: function () { |
464
|
|
|
return new this.constructor(this.models, { |
465
|
|
|
model: this.model, |
466
|
|
|
comparator: this.comparator |
467
|
|
|
}); |
468
|
|
|
}, |
469
|
|
|
|
470
|
|
|
// Define how to uniquely identify models in the collection. |
471
|
|
|
modelId: function (attrs) { |
472
|
|
|
return attrs[this.model.prototype.idAttribute || 'id']; |
473
|
|
|
}, |
474
|
|
|
|
475
|
|
|
// Private method to reset all internal state. Called when the collection |
476
|
|
|
// is first initialized or reset. |
477
|
|
|
_reset: function () { |
478
|
|
|
this.length = 0; |
479
|
|
|
this.models = []; |
480
|
|
|
this._byId = {}; |
481
|
|
|
}, |
482
|
|
|
|
483
|
|
|
// Prepare a hash of attributes (or other model) to be added to this |
484
|
|
|
// collection. |
485
|
|
|
_prepareModel: function (attrs, options) { |
486
|
|
|
if (this._isModel(attrs)) { |
487
|
|
|
if (!attrs.collection) { |
488
|
|
|
attrs.collection = this; |
489
|
|
|
} |
490
|
|
|
return attrs; |
491
|
|
|
} |
492
|
|
|
options = options ? _.clone(options) : {}; |
493
|
|
|
options.collection = this; |
494
|
|
|
var model = new this.model(attrs, options); |
|
|
|
|
495
|
|
|
if (!model.validationError) { |
496
|
|
|
return model; |
497
|
|
|
} |
498
|
|
|
this.trigger('invalid', this, model.validationError, options); |
499
|
|
|
return false; |
500
|
|
|
}, |
501
|
|
|
|
502
|
|
|
// Internal method called by both remove and set. |
503
|
|
|
_removeModels: function (models, options) { |
504
|
|
|
var removed = []; |
505
|
|
|
for (var i = 0; i < models.length; i++) { |
506
|
|
|
var model = this.get(models[i]); |
507
|
|
|
if (!model) { |
508
|
|
|
continue; |
509
|
|
|
} |
510
|
|
|
|
511
|
|
|
var index = this.indexOf(model); |
512
|
|
|
this.models.splice(index, 1); |
513
|
|
|
this.length--; |
514
|
|
|
|
515
|
|
|
// Remove references before triggering 'remove' event to prevent an |
516
|
|
|
// infinite loop. #3693 |
517
|
|
|
delete this._byId[model.cid]; |
518
|
|
|
var id = this.modelId(model.attributes); |
519
|
|
|
if (id != null) { |
520
|
|
|
delete this._byId[id]; |
521
|
|
|
} |
522
|
|
|
|
523
|
|
|
if (!options.silent) { |
524
|
|
|
options.index = index; |
525
|
|
|
model.trigger('remove', model, this, options); |
526
|
|
|
} |
527
|
|
|
|
528
|
|
|
removed.push(model); |
529
|
|
|
this._removeReference(model, options); |
530
|
|
|
} |
531
|
|
|
return removed; |
532
|
|
|
}, |
533
|
|
|
|
534
|
|
|
// Method for checking whether an object should be considered a model for |
535
|
|
|
// the purposes of adding to the collection. |
536
|
|
|
_isModel: function (model) { |
537
|
|
|
return model instanceof Model; |
538
|
|
|
}, |
539
|
|
|
|
540
|
|
|
// Internal method to create a model's ties to a collection. |
541
|
|
|
_addReference: function (model, options) { |
|
|
|
|
542
|
|
|
this._byId[model.cid] = model; |
543
|
|
|
var id = this.modelId(model.attributes); |
544
|
|
|
if (id != null) { |
545
|
|
|
this._byId[id] = model; |
546
|
|
|
} |
547
|
|
|
model.on('all', this._onModelEvent, this); |
548
|
|
|
}, |
549
|
|
|
|
550
|
|
|
// Internal method to sever a model's ties to a collection. |
551
|
|
|
_removeReference: function (model, options) { |
|
|
|
|
552
|
|
|
delete this._byId[model.cid]; |
553
|
|
|
var id = this.modelId(model.attributes); |
554
|
|
|
if (id != null) { |
555
|
|
|
delete this._byId[id]; |
556
|
|
|
} |
557
|
|
|
if (this === model.collection) { |
558
|
|
|
delete model.collection; |
559
|
|
|
} |
560
|
|
|
model.off('all', this._onModelEvent, this); |
561
|
|
|
}, |
562
|
|
|
|
563
|
|
|
// Internal method called every time a model in the set fires an event. |
564
|
|
|
// Sets need to update their indexes when models change ids. All other |
565
|
|
|
// events simply proxy through. "add" and "remove" events that originate |
566
|
|
|
// in other collections are ignored. |
567
|
|
|
_onModelEvent: function (event, model, collection, options) { |
568
|
|
|
if (model) { |
569
|
|
|
if ((event === 'add' || event === 'remove') && collection !== |
570
|
|
|
this) { |
571
|
|
|
return; |
572
|
|
|
} |
573
|
|
|
if (event === 'destroy') { |
574
|
|
|
this.remove(model, options); |
575
|
|
|
} |
576
|
|
|
if (event === 'change') { |
577
|
|
|
var prevId = this.modelId(model.previousAttributes()); |
578
|
|
|
var id = this.modelId(model.attributes); |
579
|
|
|
if (prevId !== id) { |
580
|
|
|
if (prevId != null) { |
|
|
|
|
581
|
|
|
delete this._byId[prevId]; |
582
|
|
|
} |
583
|
|
|
if (id != null) { |
|
|
|
|
584
|
|
|
this._byId[id] = model; |
585
|
|
|
} |
586
|
|
|
} |
587
|
|
|
} |
588
|
|
|
} |
589
|
|
|
this.trigger.apply(this, arguments); |
590
|
|
|
} |
591
|
|
|
|
592
|
|
|
}); |
593
|
|
|
|
594
|
|
|
// Underscore methods that we want to implement on the Collection. |
595
|
|
|
// 90% of the core usefulness of Backbone Collections is actually implemented |
596
|
|
|
// right here: |
597
|
|
|
var collectionMethods = { |
598
|
|
|
forEach: 3, |
599
|
|
|
each: 3, |
600
|
|
|
map: 3, |
601
|
|
|
collect: 3, |
602
|
|
|
reduce: 0, |
603
|
|
|
foldl: 0, |
604
|
|
|
inject: 0, |
605
|
|
|
reduceRight: 0, |
606
|
|
|
foldr: 0, |
607
|
|
|
find: 3, |
608
|
|
|
detect: 3, |
609
|
|
|
filter: 3, |
610
|
|
|
select: 3, |
611
|
|
|
reject: 3, |
612
|
|
|
every: 3, |
613
|
|
|
all: 3, |
614
|
|
|
some: 3, |
615
|
|
|
any: 3, |
616
|
|
|
include: 3, |
617
|
|
|
includes: 3, |
618
|
|
|
contains: 3, |
619
|
|
|
invoke: 0, |
620
|
|
|
max: 3, |
621
|
|
|
min: 3, |
622
|
|
|
toArray: 1, |
623
|
|
|
size: 1, |
624
|
|
|
first: 3, |
625
|
|
|
head: 3, |
626
|
|
|
take: 3, |
627
|
|
|
initial: 3, |
628
|
|
|
rest: 3, |
629
|
|
|
tail: 3, |
630
|
|
|
drop: 3, |
631
|
|
|
last: 3, |
632
|
|
|
without: 0, |
633
|
|
|
difference: 0, |
634
|
|
|
indexOf: 3, |
635
|
|
|
shuffle: 1, |
636
|
|
|
lastIndexOf: 3, |
637
|
|
|
isEmpty: 1, |
638
|
|
|
chain: 1, |
639
|
|
|
sample: 3, |
640
|
|
|
partition: 3, |
641
|
|
|
groupBy: 3, |
642
|
|
|
countBy: 3, |
643
|
|
|
sortBy: 3, |
644
|
|
|
indexBy: 3, |
645
|
|
|
findIndex: 3, |
646
|
|
|
findLastIndex: 3 |
647
|
|
|
}; |
648
|
|
|
|
649
|
|
|
// Mix in each Underscore method as a proxy to `Collection#models`. |
650
|
|
|
addUnderscoreMethods(Collection, collectionMethods, 'models'); |
651
|
|
|
|
652
|
|
|
export { |
653
|
|
|
Collection |
654
|
|
|
}; |
655
|
|
|
|